# -*- coding:utf-8 -*-
from concurrent.futures import ThreadPoolExecutor,as_completed

import threading

import time

import tkinter as tk

import re

import json

import os

import subprocess

import requests


# 防止因https证书问题报错

requests.packages.urllib3.disable_warnings()

headers = {

    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3970.5 Safari/537.36',

    'Refer'

    'er': 'https://www.bilibili.com/'

}

'''

    获取bilibili视频的主要函数

    @param url 视频页面url 结构为:url?参数

    @param p 视频p数

    @param bv 视频bv数

'''


def getBiliBiliVideo(url, p, bv):
    session = requests.session()

    res = session.get(url=url, headers=headers, verify=False)


    # 获取window.__playinfo__的json对象,[20:]表示截取'window.__playinfo__='后面的json字符串

    regex02 = re.compile(">window.__playinfo__=(.*?)</script><script>",re.S)
    videoPlayInfo = regex02.findall(res.text)[0]

    videoJson = json.loads(videoPlayInfo)

    # 获取视频链接和音频链接

    try:

        # 2018年以后的b站视频由.audio和.video组成

        videoURL = videoJson['data']['dash']['video'][0]['baseUrl']

        audioURl = videoJson['data']['dash']['audio'][0]['baseUrl']

        flag = 0
        print(videoURL)
        print(audioURl)

    except Exception:

        # 2018年以前的b站视频音频视频结合在一起,后缀为.flv

        videoURL = videoJson['data']['durl'][0]['url']

        flag = 1

    # 指定文件生成目录,如果不存在则创建目录

    dirname = ("D:/result").encode("utf-8").decode("utf-8")

    if not os.path.exists(dirname):
        os.makedirs(dirname)

        print('文件夹创建成功!')

    # 获取每一集的名称

    name = bv + "-" + str(p)

    # 下载视频和音频

    print('正在下载 "' + name + '" 的视频····')

    fileDownload(homeurl=url, url=videoURL, name='D:/result/' + name + '_Video.mp4', session=session)

    if flag == 0:
        print('正在下载 "' + name + '" 的音频····')

        fileDownload(homeurl=url, url=audioURl, name='D:/result/' + name + '_Audio.mp3', session=session)

        print('正在组合 "' + name + '" 的视频和音频····')

        # combineVideoAudio('D:/result/' + name + '_Video.mp4', 'D:/result/' + name + '_Audio.mp3','D:/result/' + name + '_output.mp4')

    print(' "' + name + '" 下载完成！')
    return True

'''
    使用session保持会话下载文件

    @param homeurl 访问来源

    @param url 音频或视频资源的链接

    @param name 下载后生成的文件名

    @session 用于保持会话
'''


def fileDownload(homeurl, url, name, session=requests.session()):
    # 添加请求头键值对,写上 refered:请求来源

    headers.update({'Referer': homeurl})

    # 发送option请求服务器分配资源

    session.options(url=url, headers=headers, verify=False)

    # 指定每次下载1M的数据

    begin = 0

    end = 1024 * 512 - 1

    flag = 0

    while True:

        # 添加请求头键值对,写上 range:请求字节范围

        headers.update({'Range': 'bytes=' + str(begin) + '-' + str(end)})

        # 获取视频分片

        res = session.get(url=url, headers=headers, verify=False)

        if res.status_code != 416:

            # 响应码不为为416时有数据

            begin = end + 1

            end = end + 1024 * 512

        else:

            headers.update({'Range': str(end + 1) + '-'})

            res = session.get(url=url, headers=headers, verify=False)

            flag = 1

        with open(name.encode("utf-8").decode("utf-8"), 'ab') as fp:

            fp.write(res.content)

            fp.flush()

        # data=data+res.content

        if flag == 1:
            fp.close()

            break


'''

    用于合并音频与视频

    @param videopath 视频路径

    @param audiopath 音频路径

    @param outpath 生成合并视频的路径

'''


def combineVideoAudio(videopath, audiopath, outpath):
    subprocess.call(("ffmpeg -i " + videopath + " -i " + audiopath + " -c copy " + outpath).encode("utf-8").decode("utf-8"), shell=True)

    os.remove(videopath)

    os.remove(audiopath)

class TkBasic(tk.Tk):
    def __init__(self):
        super().__init__()
        self.protocol("WM_DELETE_WINDOW", self.on_closing)
        self.basic_frame = tk.Frame(self, bg="#800080")
        self.basic_frame.place(relx=0, rely=0, relheight=1, relwidth=1)
        self.title("bilibili视频下载小工具")
        # 欢迎提示语
        self.welcome_label = tk.Label(self.basic_frame, text="欢迎使用bilibili视频下载工具!\n下载的资源存储在D盘的result目录里", bg="#800080",
                                       font=("微软雅黑", 18, "bold"))
        self.welcome_label.place(relx=0.1, rely=0.1, relheight=0.2, relwidth=0.8)
        # 开始语音合成入口
        self.start_text = tk.StringVar(self)
        self.bv_number = tk.StringVar(self)
        self.start_number = tk.StringVar(self)
        self.end_number = tk.StringVar(self)

        self.start_text.set("start")
        self.bv_number.set("请输入输入bv号码")
        self.start_number.set("请输入开始P号")
        self.end_number.set("请输入结束P号")

        x = self.winfo_screenmmwidth()
        y = self.winfo_screenheight()
        self.geometry("500x500+%d+%d" % (x, y / 2 * 0.5))
        self.resizable(0, 0)
        self.select_file_button = tk.Button(
            self.basic_frame, textvariable=self.start_text, bg="#800080",
            fg="black", command=self.run_get_files,
            font=("微软雅黑", 20))
        self.select_file_button.place(relx=0.7, rely=0.7, relheight=0.1, relwidth=0.2)
        #bv提示
        self.show_info_label = tk.Label(self.basic_frame, text="bv号码：", bg="#800080",
                                        font=("微软雅黑", 18, "bold"))
        self.show_info_label.place(relx=0.15, rely=0.35, relheight=0.1, relwidth=0.2)
        # bv号码输入框
        self.bv = tk.Entry(self.basic_frame, textvariable=self.bv_number,font=("微软雅黑", 12))
        self.bv.place(relx=0.35, rely=0.35, relheight=0.1, relwidth=0.3)

        self.bv.bind("<Button-1>",func=self.clear_ent01)

        # 开始号码提示
        self.start_info_label = tk.Label(self.basic_frame, text="起始P：", bg="#800080",
                                        font=("微软雅黑", 12, "bold"))
        self.start_info_label.place(relx=0.25, rely=0.45, relheight=0.1, relwidth=0.2)

        # 开始号码
        self.start_p = tk.Entry(self.basic_frame, textvariable=self.start_number)
        self.start_p.place(relx=0.4, rely=0.47, relheight=0.05, relwidth=0.2)

        self.start_p.bind("<Button-1>", func=self.clear_ent02)

        # 结束号码提示
        self.end_info_label = tk.Label(self.basic_frame, text="结束P：", bg="#800080",
                                         font=("微软雅黑", 12, "bold"))
        self.end_info_label.place(relx=0.25, rely=0.55, relheight=0.1, relwidth=0.2)

        # 结束号码
        self.end_p = tk.Entry(self.basic_frame, textvariable=self.end_number)
        self.end_p.place(relx=0.4, rely=0.57, relheight=0.05, relwidth=0.2)

        self.end_p.bind("<Button-1>", func=self.clear_ent03)

        self.clear_status_01 = True
        self.clear_status_02 = True
        self.clear_status_03 = True

        # 显示进度条
        self.run_label = tk.Label(self.basic_frame, text="正在下载，请耐心等待...", bg="#800080",
                                         font=("微软雅黑", 20, "bold"))
        self.download_label = tk.Label(self.basic_frame, text="下载完成！", bg="#800080",
                                         font=("微软雅黑", 20, "bold"))
        self.run_label.place_forget()
        self.download_label.place_forget()

    def clear_ent01(self,ev):
        if self.clear_status_01:
            self.bv_number.set("")
            self.clear_status_01 = False


    def clear_ent02(self,ev):
        if self.clear_status_02:
            self.start_number.set("")
            self.clear_status_02 = False

    def clear_ent03(self,ev):
        if self.clear_status_03:
            self.end_number.set("")
            self.clear_status_03 = False



    def on_closing(self):
        self.destroy()

    def run_get_files(self):
        self.run()

    def start(self):
        self.mainloop()

    def run(self):
        # 隐藏
        self.welcome_label.place_forget()
        self.select_file_button.place_forget()
        self.bv.place_forget()
        self.show_info_label.place_forget()
        self.start_p.place_forget()
        self.end_p.place_forget()
        self.start_info_label.place_forget()
        self.end_info_label.place_forget()
        # 显示
        self.run_label.place(relx=0.05, rely=0.3, relheight=0.3, relwidth=1)

        t = threading.Thread(target=self.downloadfiles)
        t.setDaemon(True)
        t.start()



    def downloadfiles(self):
        # 下载操作
        bv = self.bv.get().strip()

        url = 'https://www.bilibili.com/video/' + bv

        # 选择视频从第几p开始到第几p结束

        startPart = self.start_p.get().strip()

        endPart = self.end_p.get().strip()

        try:
            with ThreadPoolExecutor(max_workers=9) as t:
                obj_list = []
                begin = time.time()
                for p in range(int(startPart), int(endPart) + 1):
                    obj = t.submit(getBiliBiliVideo, url + '?p=' + str(p), p, bv)
                    obj_list.append(obj)

                for future in as_completed(obj_list):
                    data = future.result()
                    print(data)
                    print('*' * 50)
                times = time.time() - begin
                print(times)
        except:
            pass
        # 隐藏
        self.run_label.place_forget()

        # 提示下载完成
        self.download_label.place(relx=0.05, rely=0.3, relheight=0.3, relwidth=1)
        time.sleep(3)
        # 隐藏下载完成提示
        self.download_label.place_forget()

        # 下载完成后显示
        self.welcome_label.place(relx=0.1, rely=0.1, relheight=0.2, relwidth=0.8)
        self.select_file_button.place(relx=0.7, rely=0.7, relheight=0.1, relwidth=0.2)
        self.bv.place(relx=0.35, rely=0.35, relheight=0.1, relwidth=0.3)
        self.show_info_label.place(relx=0.15, rely=0.35, relheight=0.1, relwidth=0.2)
        self.start_p.place(relx=0.4, rely=0.47, relheight=0.05, relwidth=0.2)
        self.end_p.place(relx=0.4, rely=0.57, relheight=0.05, relwidth=0.2)
        self.start_info_label.place(relx=0.25, rely=0.45, relheight=0.1, relwidth=0.2)
        self.end_info_label.place(relx=0.25, rely=0.55, relheight=0.1, relwidth=0.2)

if __name__ == '__main__':
    import multiprocessing
    multiprocessing.freeze_support()
    case_task = TkBasic()
    case_task.start()




